function painterSortStable(a, b) {

  if (a.groupOrder !== b.groupOrder) {

    return a.groupOrder - b.groupOrder;

  } else if (a.renderOrder !== b.renderOrder) {

    return a.renderOrder - b.renderOrder;

  } else if (a.program !== b.program) {

    return a.program.id - b.program.id;

  } else if (a.material.id !== b.material.id) {

    return a.material.id - b.material.id;

  } else if (a.z !== b.z) {

    return a.z - b.z;

  } else {

    return a.id - b.id;

  }

}

function reversePainterSortStable(a, b) {

  if (a.groupOrder !== b.groupOrder) {

    return a.groupOrder - b.groupOrder;

  } else if (a.renderOrder !== b.renderOrder) {

    return a.renderOrder - b.renderOrder;

  } else if (a.z !== b.z) {

    return b.z - a.z;

  } else {

    return a.id - b.id;

  }

}


function WebGLRenderList(properties) {

  const renderItems = [];
  let renderItemsIndex = 0;

  const opaque = [];
  const transparent = [];

  const defaultProgram = {id: -1};

  function init() {

    renderItemsIndex = 0;

    opaque.length = 0;
    transparent.length = 0;

  }

  function getNextRenderItem(object, geometry, material, groupOrder, z, group) {

    let renderItem = renderItems[renderItemsIndex];
    const materialProperties = properties.get(material);

    if (renderItem === undefined) {

      renderItem = {
        id: object.id,
        object: object,
        geometry: geometry,
        material: material,
        program: materialProperties.program || defaultProgram,
        groupOrder: groupOrder,
        renderOrder: object.renderOrder,
        z: z,
        group: group
      };

      renderItems[renderItemsIndex] = renderItem;

    } else {

      renderItem.id = object.id;
      renderItem.object = object;
      renderItem.geometry = geometry;
      renderItem.material = material;
      renderItem.program = materialProperties.program || defaultProgram;
      renderItem.groupOrder = groupOrder;
      renderItem.renderOrder = object.renderOrder;
      renderItem.z = z;
      renderItem.group = group;

    }

    renderItemsIndex++;

    return renderItem;

  }

  function push(object, geometry, material, groupOrder, z, group) {

    const renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

    (material.transparent === true ? transparent : opaque).push(renderItem);

  }

  function unshift(object, geometry, material, groupOrder, z, group) {

    const renderItem = getNextRenderItem(object, geometry, material, groupOrder, z, group);

    (material.transparent === true ? transparent : opaque).unshift(renderItem);

  }

  function sort(customOpaqueSort, customTransparentSort) {

    if (opaque.length > 1) opaque.sort(customOpaqueSort || painterSortStable);
    if (transparent.length > 1) transparent.sort(customTransparentSort || reversePainterSortStable);

  }

  function finish() {

    // Clear references from inactive renderItems in the list

    for (let i = renderItemsIndex, il = renderItems.length; i < il; i++) {

      const renderItem = renderItems[i];

      if (renderItem.id === null) break;

      renderItem.id = null;
      renderItem.object = null;
      renderItem.geometry = null;
      renderItem.material = null;
      renderItem.program = null;
      renderItem.group = null;

    }

  }

  return {

    opaque: opaque,
    transparent: transparent,

    init: init,
    push: push,
    unshift: unshift,
    finish: finish,

    sort: sort
  };

}

function WebGLRenderLists(properties) {

  let lists = new WeakMap();

  function get(scene, camera) {

    const cameras = lists.get(scene);
    let list;

    if (cameras === undefined) {

      list = new WebGLRenderList(properties);
      lists.set(scene, new WeakMap());
      lists.get(scene).set(camera, list);

    } else {

      list = cameras.get(camera);
      if (list === undefined) {

        list = new WebGLRenderList(properties);
        cameras.set(camera, list);

      }

    }

    return list;

  }

  function dispose() {

    lists = new WeakMap();

  }

  return {
    get: get,
    dispose: dispose
  };

}


export {WebGLRenderLists, WebGLRenderList};
